-module(ffreq).

-export([start/0, add_file/3, wait_file/3, get_dict/1, print/2]).

-define(BUFFER_SIZE, 1024).

start() ->
    spawn_link(fun() -> main_loop(dict:new()) end).

add_file(Pid, FileName, WhiteSpaces) ->
    Pid ! {add_file, {self(), FileName, WhiteSpaces}},
    ok.

wait_file(Pid, FileName, Timeout) ->
    receive
        {Pid, {file_result, {FileName, Result}}} -> Result
    after Timeout ->
            timeout
    end.

get_dict(Pid) ->
    Pid ! {get_dict, self()},
    receive
        {Pid, {dict, Result}} -> {ok, Result}
    after 100                 -> {error, timeout}
    end.

print(Dict, Verbose) ->
    if
        Verbose -> io:format("Character    Frequency~n");
        true    -> ok
    end,

    TotalCount = dict:fold(fun(_, Freq, Result) -> Result + Freq end, 0, Dict),
    lists:foreach(fun({Char, Freq}) ->
                          PrintList = [[Char], Freq, Freq / TotalCount * 100],
                          if
                              Verbose -> io:format("\'~s\'          ~w (~.2f%)~n", PrintList);
                              true    -> io:format("\'~s\' ~w ~.2f%~n", PrintList)
                          end
                  end,
                  lists:sort(fun({_, F1}, {_, F2}) -> F2 < F1 end,
                                 dict:to_list(Dict))).

main_loop(Dict) ->
    receive
        {add_file, {Pid, FileName, WhiteSpaces}} ->
            CtlPid = self(),
            spawn_link(fun() -> file_reader(Pid, CtlPid, FileName, WhiteSpaces) end),
            main_loop(Dict);
        {get_dict, Pid} ->
            Pid ! {self(), {dict, Dict}},
            main_loop(Dict);
        {merge, {D, ClientPid, FileName}} ->
            ClientPid ! {self(), {file_result, {FileName, ok}}},
            main_loop(dict:merge(fun(_, F1, F2) -> F1 + F2 end, Dict, D));
        {{error, _} = Error, {FileName, ClientPid}} ->
            ClientPid ! {self(), {file_result, {FileName, Error}}},
            main_loop(Dict);
        _ ->
            main_loop(Dict)
    end.

file_reader(ClientPid, CtlPid, FileName, WhiteSpaces) ->
    case make_freq_dict(FileName, WhiteSpaces, dict:new()) of
        {ok, Dict}         -> CtlPid ! {merge, {Dict, ClientPid, FileName}};
        {error, _} = Error -> CtlPid ! {Error, {FileName, ClientPid}}
    end.

make_freq_dict(FileName, WhiteSpaces, Dict) ->
    case file:open(FileName, [read]) of
        {ok, FD} ->
            Result   = make_freq_dict_impl(FD, WhiteSpaces, Dict),
            file:close(FD),
            Result;
        {error, _} = Error ->
            Error
    end.

make_freq_dict_impl(FD, WhiteSpaces, Result) ->
    case file:read(FD, ?BUFFER_SIZE) of
        {ok, Data}         -> make_freq_dict_impl(FD, WhiteSpaces,
                                                  store_data(Data, WhiteSpaces, Result));
        eof                -> {ok, Result};
        {error, _} = Error -> Error
    end.

store_data(Data, WhiteSpaces, Dict) ->
    lists:foldl(fun(Char, Result) ->
                        case lists:any(fun(X) -> X =:= Char end, WhiteSpaces) of
                            true  -> Result;
                            false ->
                                case dict:find(Char, Result) of
                                    {ok, Value} -> dict:store(Char, Value + 1, Result);
                                    error       -> dict:store(Char, 1, Result)
                                end
                        end
                end, Dict, Data).
